use actix_web::{HttpResponse, HttpRequest, web::Json};
use fluffy::{
    response, random, datetime, utils, db, 
    model::{Model, Db}, request, jwt, 
};
use qrcode_generator::QrCodeEcc;
use redis::Commands;
use crate::common::{
    HOST_NAME, ShortLink, LoginToken, 
    LOGIN_ERROR_MAX, LOGIN_ERROR_KEY, LOGIN_ERROR_FIRST, 
    LOGIN_CALL_COUNT, NOT_LOGIN_CALL_COUNT, URL_EXPIRED,
};
use crate::models::{UserLogin, UserRegister, Links, Users, UserCodes};
use crate::validations::index;

/// 增加登录错误次数
macro_rules! login_error_incr  {
    ($cache: expr, $hash_key: expr, $counter: expr) => ({
        $cache.hincr::<&str, &str, usize, usize>($hash_key, LOGIN_ERROR_KEY, $counter).unwrap();
    });
}

/// 如果没有设置首次登录错误时间, 则设置首次登录错误时间
macro_rules! login_error_init {
    ($cache: expr, $hash_key: expr) => {
        if $cache.hget::<&str, &str, u64>($hash_key, LOGIN_ERROR_FIRST).unwrap_or(0) == 0 {
            $cache.hset::<&str, &str, u64, usize>($hash_key, LOGIN_ERROR_FIRST, datetime::timestamp()).unwrap_or(0);
        }
    }
}

#[derive(Serialize, Default, Debug, Deserialize)]
pub struct TransInfo { 
    pub origin: String,
    pub key: String,
}

#[derive(Serialize, Default, Debug, Deserialize)]
pub struct TransUrl { 
    pub url: String,
}
    
/// 主页
pub fn index() -> HttpResponse { 
    response::ok()
}

/// 转换url
pub async fn short_url(req: HttpRequest) -> HttpResponse { 
    let code = req.match_info().get("code").unwrap_or("");
    if code == "" { 
        return response::error("错误的短链接");
    }
    if code.len() != 6 { 
        return response::error("短链接解码失败");
    }
    
    let key = format!("urls-{}", code);
    let mut cache = fluffy::cache::get_conn();
    let url = cache.get::<&str, String>(&key).unwrap_or("".to_owned());
    if url == "" { 
        return response::error("短链接获取失败");
    }
    let result = TransUrl { url };
    response::result(&result)
}

/// 转换为短网址
/// 生成的base64字符串, 前面需加: "data:image/png;base64,"
/// 先检测是用户还是游客
pub async fn trans(req: HttpRequest, data: Json<TransInfo>) -> HttpResponse {
    if !data.origin.starts_with("http://") && !data.origin.starts_with("https://") {
        return response::error("请输入正确格式的网址, 前缀应有 https:// 或 http://");
    }
    if data.key != "" && data.key.len() != 16 { 
        return response::error("错误的APP-KEY");
    }
    let ymd = datetime::now().format("%Y%m%d").to_string();
    let mut cache = fluffy::cache::get_conn();
    let ip = request::get_ip(&req); //来源ip
    let mut is_guest = true;
    let mut user_id = 0;
    let mut user_name = String::new();
    let hash_key = if data.key != "" { 
        let value = if let Ok(v) = cache.hget::<&str, &str, String>("user-keys", &data.key) { v } else { 
            return response::error("用户KEY权限检测失败");
        }; //值是 id|name 这样的形式
        let arr = value.split("|").collect::<Vec<&str>>();
        if arr[0] != "" && arr[1] != "" { 
            if let Ok(v) = arr[0].parse::<usize>() { user_id = v; }
            user_name = arr[1].to_owned();
            is_guest = false;
            format!("trans-user-{}-{}", arr[0], ymd)  //记录用户调用的key
        } else { 
            format!("trans-guest-{}-{}", ip, ymd) //记录游客调用的key
        }
    } else { 
        format!("trans-guest-{}-{}", ip, ymd) //同上
    };
    let call_times = cache.hget::<&str, &str, usize>(&hash_key, "call-times").unwrap_or(0); //当前调用次数
    if is_guest && call_times >= NOT_LOGIN_CALL_COUNT || !is_guest && call_times >= LOGIN_CALL_COUNT { 
        return response::error(&format!("调用次数达到上限{}次/天", if is_guest { NOT_LOGIN_CALL_COUNT } else { LOGIN_CALL_COUNT }));
    }

    // 先从缓存当中读取数据
    let url_md5 = utils::md5_str(&data.origin);
    let url_short_key = format!("code-{}", &url_md5);
    let url_short_png = format!("base-{}", &url_md5);
    let url_short_code = cache.get::<&str, String>(&url_short_key).unwrap_or("".to_owned()); //从缓存当中读取短网址
    if url_short_code != "" { 
        let url_short_base = cache.get::<&str, String>(&url_short_png).unwrap_or("".to_owned()); //从缓存当中读取图片
        if url_short_base != "" { 
            let result = ShortLink { 
                url: format!("{}/{}", HOST_NAME, url_short_code),
                code: url_short_base,
            };
            return response::result(&result);
        }
    }

    // 如果缓存没有,则生成,并保存在缓存当中
    loop {
        let rand_str = random::rand_str(6).to_owned(); //生成加密字符串
        if !cache.hexists::<&str, &str, bool>("short-codes", &rand_str).unwrap() { //判断是否在缓存当中已经存在
            let short_code = qrcode_generator::to_png_to_vec(&data.origin, QrCodeEcc::Low, 128).unwrap();
            let short_base = base64::encode(&short_code).to_string();
            // 写入缓存
            let url_key = format!("urls-{}", &rand_str);
            cache.set_ex::<&str, &str, String>(&url_short_key, &rand_str, URL_EXPIRED).unwrap(); //写入缓存: 短链接代码
            cache.set_ex::<&str, &str, String>(&url_short_png, &short_base, URL_EXPIRED).unwrap(); //写入缓存: 图片二维码
            cache.set_ex::<&str, &str, String>(&url_key, &data.origin, URL_EXPIRED).unwrap(); //写入缓存: 原始网址
            cache.hincr::<&str, &str, usize, usize>(&hash_key, "call-times", 1).unwrap(); //对调用次数进行累加
            let now = datetime::timestamp();
            if !is_guest { // 如果不是游客, 则写入数据库当中
                let data_insert = create_row![
                    "ymd" => &ymd,
                    "user_id" => &user_id,
                    "user_name" => &user_name,
                    "origin_url" => &data.origin,
                    "code" => &rand_str,
                    "image" => &short_base,
                    "created" => &now,
                ];
                //println!("query = {}", data_insert);
                let mut conn = db::get_conn();
                let _ = Links::create(&mut conn, &data_insert);
                //println!("res = {:?}", res);
            }
            let result = ShortLink {
                url: format!("{}/{}", HOST_NAME, rand_str.to_owned()),
                code: short_base,
            };
            return response::result(&result);
        }
    };
}

/// 登录
pub async fn login(req: HttpRequest, login: Json<UserLogin>) -> HttpResponse {
    let ip = request::get_ip(&req);
    let hash_key = &format!("ip-{}", ip);
    let mut cache = fluffy::cache::get_conn();
    // 判断此ip是否被封禁
    if cache.hget::<&str, &str, bool>(hash_key, "disabled").unwrap() {
        return response::error("此IP已经被封禁");
    }
    // 判断此ip出错次数
    let now = datetime::timestamp() as i64;
    let first_error_time = cache.hget::<&str, &str, u64>(hash_key, LOGIN_ERROR_FIRST).unwrap_or(0) as i64;
    if first_error_time > 0 &&
        now - first_error_time < 36000 &&
        cache.hget::<&str, &str, usize>(hash_key, LOGIN_ERROR_KEY).unwrap_or(0) >= LOGIN_ERROR_MAX {
        return response::error("登录次数过多, 请过10个小时以后重新登录");
    }
    // 检测用户名称及密码
    let check_result = index::login(&login);
    if check_result.is_err() { 
        //println!("result = {:?}", check_result);
        let err = check_result.err().unwrap();
        return response::error(&err);
    }
    let mut conn = db::get_conn();
    let query = query![ fields => "id, password, secret, state, login_count", ];
    let cond = cond![ "name" => &login.name, ];
    let row = Users::fetch_row(&mut conn, &query, Some(&cond));
    if row.is_none() {
        login_error_incr!(cache, hash_key, 1);
        login_error_init!(cache, hash_key);
        return response::error("用户名称或密码错误");
    }
    let (id, password, secret, state, login_count): (usize, String, String, usize, usize) = from_row!(row.unwrap());
    if state != 1 {
        login_error_incr!(cache, hash_key, 1);
        login_error_init!(cache, hash_key);
        return response::error("用户状态异常, 不能登录");
    }
    let login_password = utils::get_password(&login.password, &secret);
    if login_password != password {
        login_error_incr!(cache, hash_key, 1);
        login_error_init!(cache, hash_key);
        return response::error("用户名称或者密码错误");
    }
    // 更新数据库当中的信息
    let secret_new = random::rand_str(32);
    let password_new = utils::get_password(&login.password, &secret_new);
    let data = update_row![
        "password" => &password_new,
        "secret" => &secret_new,
        "login_count" => &(login_count + 1),
        "last_ip" => &ip,
        "last_login" => &now,
    ];
    let cond_update = cond!["id" => &id, ];
    let affected_rows = Users::update(&mut conn, &data, &cond_update);
    if affected_rows == 0 {
        login_error_incr!(cache, hash_key, 1);
        login_error_init!(cache, hash_key);
        return response::error("更新用户登录信息失败");
    }
    cache.hset::<&str, &str, usize,usize>(hash_key, LOGIN_ERROR_FIRST, 0).unwrap(); //将第一次出错时间设为0
    cache.hset::<&str, &str, usize, usize>(hash_key, LOGIN_ERROR_KEY, 0).unwrap(); //将出错次数设为0
    let token = jwt::encode(id, &login.name);
    let token_key = format!("token-{}", utils::md5_str(&token));
    let token_val = format!("{}|{}", &id, &login.name);
    cache.set_ex::<&str, &str, bool>(&token_key, &token_val, 86400).unwrap(); //写入到缓存当中
    let login_token = LoginToken { token };
    response::result(&login_token)
}

/// 注册
pub async fn register(req: HttpRequest, reg: Json<UserRegister>) -> HttpResponse {
    let result = index::register(&reg);
    if result.is_err() {
        let err = result.err().unwrap();
        return response::error(&err);
    }

    // 检测账号是否已经被注册
    let mut conn = db::get_conn();
    let sql_find = format!("SELECT id FROM users WHERE name = '{}'", &reg.name);
    let row = Db::query_first(&mut conn, &sql_find);
    if row.is_some() {
        return response::error("此用户名称已经被注册");
    }

    let ip = request::get_ip(&req);
    // 检测此ip注册是否达到上限
    let sql_ip = format!("SELECT COUNT(id) AS cot FROM users WHERE reg_ip = '{}'", ip);
    match Db::query_first(&mut conn, &sql_ip) {
        Some(v) => {
            let total: usize = from_row!(v);
            if total > 5 { return response::error("每个IP仅限于5个账号注册"); }
        },
        None => {}
    };

    // 写入注册信息到数据库
    let secret = random::rand_str(32);
    let password_new = utils::get_password(&reg.password, &secret);
    let now = datetime::timestamp();
    let data = create_row![
        "name" => &reg.name,
        "password" => &password_new,
        "secret" => &secret,
        "reg_ip" => &ip, 
        "created" => &now,
        "updated" => &now,
    ];
    let insert_id = Users::create(&mut conn, &data);   
    if insert_id == 0 {
        return response::error("用户注册失改");
    }

    let code = random::rand_str(16);
    let key_val = format!("{}|{}", insert_id, &reg.name);
    let mut cache = fluffy::cache::get_conn();
    cache.hset::<&str, &str, &str, usize>("user-keys", &code, &key_val).unwrap();
    let data_code = create_row![
        "user_id" => &insert_id,
        "user_name" => &reg.name,
        "code" => &code,
        "created" => &now,
    ];
    let _ = UserCodes::create(&mut conn, &data_code);

    response::ok()
}
